package tv.geir.slackspace.matching;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tv.geir.slackspace.util.PrimitiveUtil;

/**
 * Use this class to compare two instances. If the classes being compared
 * implement <code>java.lang.Comparable</code> all operators in Operators can
 * be used (not just equals).
 * 
 * @author Geir Gullestad Pettersen
 * 
 */
public class ValueComparator {

    private static Log log = LogFactory.getLog(ValueComparator.class);

    /**
     * Evaluates input using the following expression and returns a boolean
     * representing outcome of the evaluation:
     * 
     * <pre>
     *  instance operator search
     *  
     *  example:
     *  instance EQUALS search
     *  instance GREATER_THAN search
     *  instance LESS_THAN search
     *  
     *  // will return true (1 is greater than 0) 
     *  evaluate( new Integer(1), Operand.GREATER_THAN, new Integer(0), false );
     *  
     *  // will return false (0 is not greater than 0)
     *  evaluate( new Integer(0), Operand.GREATER_THAN, new Integer(0), false );
     * </pre>
     * 
     * If <code>search</code> is <code>null</code> it will be treated as a
     * wildcard and will match any value that <code>instance</code> may have.
     * If instance is a primitive it is not possible to give it a null-value. To
     * use a primitive as a wildcard, <code>defaultPrimitiveIsWildcard</code>
     * should be set to <code>true</code>, otherwise it should always be
     * <code>false</code>.
     * <p>
     * For for all operators but EQUALS to work the objects that are being
     * compared have to be instances of a a class that implements
     * <code>java.lang.Comparable</code>.
     * 
     * @param instance
     * @param operand
     * @param search
     * @param defaultPrimitiveIsWildcard
     *            if set to true and <code>search</code> is a primitive (not
     *            an object), it will be treated as a wildcard if represents the
     *            default value for that type
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    public boolean evaluate(Object instance, Operator operator, Object search,
            boolean defaultPrimitiveIsWildcard) {
        boolean match = true;
        if (operator == null) {
            throw new NullPointerException("Operand must have a value ("
                    + Operator.values() + ")");
        }
        
        
        
        /*
         * Do comparison if:
         * Search is not a wildcard 
         * Not A primitive with default value &&
         * primitiveDefaultValueWildcard == false
         */
        if (search != null 
                && (!defaultPrimitiveIsWildcard
                || !new PrimitiveUtil().isPrimitiveWithDefaultValue( search )) ) {
            

            // evaluate values
            try {
                switch (operator) {
                case EQUALS:
                    match = search.equals(instance);                    
                    break;
                case GREATER_THAN:
                    if (instance instanceof Comparable) {
                        match = ((Comparable) instance).compareTo(search) > 0;
                    }
                    break;
                case LESS_THAN:
                    if (instance instanceof Comparable) {
                        match = ((Comparable) instance).compareTo(search) < 0;
                    }
                    break;
                case GREATER_THAN_EQUALS:
                    if (instance instanceof Comparable) {
                        match = ((Comparable) instance).compareTo(search) > 0;
                    }
                    // if not greater, check if equals
                    match = match ? match : search.equals( instance );
                    break;
                case LESS_THAN_EQUALS:
                    if (instance instanceof Comparable) {
                        match = ((Comparable) instance).compareTo(search) < 0;
                    }
                    // if not less, check if equals
                    match = match ? match : search.equals( instance );
                    break;
                }
                
            } catch (Throwable t) {
                // compareTo may throw an exception . log it and flag match as false
                log.warn( "evaluation failed: " + t.getMessage(), t );
                match = false;
            }
        }
        return match;
    }

    /**
     * Evaluates input using the following expression and returns a boolean
     * representing outcome of the evaluation:
     * 
     * <pre>
     *  instance operator search
     *  
     *  example:
     *  instance EQUALS search
     *  instance GREATER_THAN search
     *  instance LESS_THAN search
     *  instance GREATER_THAN_EQUALS search
     *  instance LESS_THAN_EQUALS search
     *  
     *  // will return true (1 is greater than 0) 
     *  evaluate( new Integer(1), Operand.GREATER_THAN, new Integer(0), false );
     *  
     *  // will return false (0 is not greater than 0)
     *  evaluate( new Integer(0), Operand.GREATER_THAN, new Integer(0), false );
     * </pre>
     * 
     * If <code>search</code> is <code>null</code> it will be treated as a
     * wildcard and will match any value that <code>instance</code> may have.
     * If instance is a primitive it is not possible to give it a null-value. To
     * use a primitive as a wildcard, <code>defaultPrimitiveIsWildcard</code>
     * should be set to <code>true</code>, otherwise it should always be
     * <code>false</code>.
     * <p>
     * For for all operators but EQUALS to work the objects that are being
     * compared have to be instances of a a class that implements
     * <code>java.lang.Comparable</code>.
     * 
     * @param instance
     * @param operand
     * @param search
     * 
     * @return
     */
    public boolean evaluate(Object instance, Operator operator, Object search ){
        return evaluate( instance, operator, search, false );
    }
            
    
    

}
